home *** CD-ROM | disk | FTP | other *** search
- /************************************************************************
- * LISP mode for Epsilon -- Steve Ward's variant 3/90 *
- * *
- * Ancestory: *
- * .SCM file suffix added 1991 Aubrey Jaffer. *
- * Portions Copyright (C) 1985 Robert C. Pettengill *
- * "Permission is granted to reproduce and use this copyrighted *
- * material for any purpose whatsoever." *
- * Modified by BKPH at MIT; extensive modifications by SAW *
- * *
- * Additional evolution is encouraged; updates appreciated (email to *
- * ward@mit.edu) *
- * *
- * *
- ************************************************************************
- * *
- * LISP MODE: Auto-entered on editing .LSP, .S, .SCM (Scheme) files. *
- * ) } ] Shows (blinks) matching delimiter. *
- * TAB Indents current line. Multiple consecutive tabs typed drop *
- * back to standard tab stops, spaced at lisp-tab-size. *
- * ENTER Starts new line at our allegedly correct indent *
- * *
- * A-a Moves to start of current definition (looks for "(" in col 0)*
- * A-e Moves past end of current definition *
- * A-g Selects (point, mark) current definition *
- * A-tab Reformats current top-level definition (indents, not fills) *
- * With arg (^U A-tab) reformats current subexpression. *
- * REPEATED uses: cycle thru selected lisp_indent_modes, *
- * reformating same expr/subexpr in various ways; a good way *
- * to explore different indentation formats. *
- * A-b Backward S-Expression *
- * A-; Comment: moves to comment column, adds ; *
- * C-A-u Up a level: moves left to start of current (...) form *
- * C-A-d Down a level: moves right past next ( *
- * *
- * Should be done, but haven't been: *
- * A-F Forward S-Expression. Needs forward_thing(), analogous to *
- * backward_thing() q.v. Actually it should be simpler. *
- * A-DEL Backward kill S-expression *
- * *
- * Relics of previous incarnations, largely superceded by above: *
- * C-A-F forward-level (use A-F?) *
- * C-A-B backward-level (use A-B?) *
- * C-A-K kill-level: Kills next (...) form *
- * A-<del> backward-kill-level *
- * *
- ************************************************************************
- * *
- * WARNINGS: *
- * *
- * (1) Much of what follows is based on sloppy heuristics, rather *
- * than precise algorithms. This includes details such as *
- * handling of comments - unbalanced parens in comments, eg, *
- * will fool this code. Likewise, it is naive about strings, etc *
- * *
- * It is perfectly possible to write C code to take these factors *
- * properly into account; unfortunately, that involves coding the *
- * low-level char-by-char scanning in EEL rather than using *
- * searches and is intolerably slow. If you'd like to play with *
- * this approach, the key routine to make smarter is *
- * lisp_backward_thing(), qv. *
- * *
- * (2) Top-level forms are presumed to be exactly those which begin *
- * in column 0 (as opposed to those starting with "(def", etc). *
- * *
- * (3) My indentation heuristic tries to align each line with the *
- * start of the previous S-expression (ignoring comments), except *
- * in the special case where the line containing the start of that *
- * S-expression begins a top-level form (detected by a left paren *
- * in column 0). In the latter case, the line is just inset by *
- * one tab stop. *
- * *
- ************************************************************************
- * *
- * User-hackable variables: *
- * *
- * int lisp-comment-col (default=40) *
- * *
- * int lisp-tab-size: tab stop interval (default=2) *
- * *
- * int lisp_tab_mode: controls action on TAB key. Interesting values: *
- * 0: conventional tabs, like text mode. *
- * 1226: A=2, X=1, Y=3, Z=2. The default. Behavior is: *
- * First tab tries to use heuristic indent; from there on, *
- * additional tabs afford primitive control over formatting. *
- * *
- * Word is parsed as follows: A + 8*x + 64*Y + 512*Z, where *
- * A: 2-bit field controlling action when tab typed outside *
- * of indententation (left-hand white space): *
- * 0: raw tabs: inserts tab chars into buffer. *
- * 1: conventional tabs, set at lisp_tab_size intervals. *
- * 2: not recognized as special case (handled as below) *
- * X: Field controlling action on 1st tab typed *
- * Y: Field controlling action on 2nd tab typed *
- * Z: Field controlling action on 3rd & successive tabs typed *
- * Each 3-bit field can have the following values: *
- * 0: raw tabs: inserts tab chars into buffer. *
- * 1: set indent according to heuristic *
- * 2: increase indent by one lisp_tab_size *
- * 3: set indent to 1st tab stop (column lisp_tab_size) *
- * 4: set indent to left margin *
- * 5: conventional tabs, set at lisp_tab_size intervals. *
- * *
- * int lisp_indent_mode: controls choice of indenting heuristic. *
- * Microcoded; turning bits OFF adds hacks. *
- * 0: No indent; always assumes line 0. *
- * 1: Always same as previous line. *
- * 16: My heuristic (as explained above), wide-mode. Aligns with *
- * last form at same level. *
- * 16+1: My heuristic, without the top-level-detect hack. *
- * 16+2: My heuristic, narrow-mode. Tries to align with the left- *
- * most previous form on the same level. *
- * 16+4: Avoid going beyond tab stop after one at which containing *
- * form starts. BKPH's hack of indenting 1 if the car of that *
- * containing form is compound, else tabbing, is maintained. *
- * 16+8: My heuristic, compromise-mode. Aligns with 2nd form at *
- * current level, rather than left-most. *
- * *
- * *
- ************************************************************************/
-
- /************************************************************************
- * *
- * Following are samples of indenting modes: *
- * *
- ************************************************************************
-
-
- ; mode=23:
- (define (foo bar)
- (cond ((eq foo bar)
- (prog nil
- (setq bar foo)
- (setq foo bar)))
- ((atom foo)((bar foo) bar))
- (t (foo (foo (foo (foo bar bar)
- (bar bar foo) ))))))
-
- ; mode=22:
- (define (foo bar)
- (cond ((eq foo bar)
- (prog nil
- (setq bar foo)
- (setq foo bar)))
- ((atom foo)((bar foo) bar))
- (t (foo (foo (foo (foo bar bar)
- (bar bar foo) ))))))
-
-
-
- ; mode=20: "Steve mode"
- (define (foo bar)
- (cond ((eq foo bar)
- (prog nil
- (setq bar foo)
- (setq foo bar)))
- ((atom foo)((bar foo) bar))
- (t (foo (foo (foo (foo bar bar)
- (bar bar foo) ))))))
-
- ; mode=20 again, typed differently:
- (define (foo bar)
- (cond
- ((eq foo bar)
- (prog nil
- (setq bar foo)
- (setq foo bar)))
- ((atom foo)((bar foo) bar))
- (t (foo (foo (foo (foo bar bar)
- (bar bar foo) ))))))
-
-
- ; mode=19: pure "BKPH mode"
- (define (foo bar)
- (cond ((eq foo bar)
- (prog nil
- (setq bar foo)
- (setq foo bar)))
- ((atom foo)((bar foo) bar))
- (t (foo (foo (foo (foo bar bar)
- (bar bar foo) ))))))
-
-
- ; mode=28, super-compact mode
- (define (foo bar)
- (cond
- ((eq foo bar)
- (prog nil
- (setq bar foo)
- (setq foo bar)))
- ((atom foo)((bar foo) bar))
- (t (foo (foo (foo (foo bar bar)
- (bar bar foo) ))))))
-
-
- ************************************************************************
- * *
- * End of indenting mode samples. *
- * *
- ************************************************************************/
-
-
-
-
- #include "eel.h"
-
-
- /************************************************************************
- * *
- * User-hackable variables: *
- * *
- ************************************************************************/
-
- int lisp_comment_col = 40; /* column for lisp comments to begin */
- int lisp_tab_size = 2; /* spacing between tab stops */
-
- int lisp_indent_mode = 2; /* Indentation heuristic */
-
- int lisp_tab_mode = 2 /* Field A: tab outside of indentation */
- +( 8* 1) /* Field X: first tab typed */
- +( 64* 3) /* Field Y: 2nd tab typed */
- +(512* 2); /* Field Z: successive tabs typed. */
-
-
- /************************************************************************
- * *
- * Internals: *
- * *
- ************************************************************************/
-
- keytable lisp_tab; /* key table for lisp mode */
-
- #define LEFTD '(' /* Delimiters. NB: CHARACTERS, not */
- #define RIGHTD ')' /* STRINGS (as elsewhere in EEL) */
-
- /* These values are chosen pretty randomly... somebody should check to
- * see if theres a systematic way to allocate them:
- */
- #define LISP_TAB_1 69 /* this_cmd code for first tab command */
- #define LISP_TAB_2 70 /* this_cmd code for 2nd+ tab command */
- #define LISP_REFORMAT_TOP 71 /* Reformat top-level expression. */
- #define LISP_REFORMAT_SUB 72 /* Reforamt subexpression. */
-
-
-
- /* Main indentation heuristic:
- *
- * lisp_compute_indent() returns the column (0-indexed) that it thinks
- * the current line should begin at. No side-effects; point unchanged.
- * This is the first place to hack if you don't like the current format.
- *
- * Current heuristic:
- * (1) Aligns with start of last "thing" (approximately, S-expr) on
- * previous line; EXCEPT
- * (2) If previous line starts in column 0, starts one lisp-tab-size
- * indented. Here "previous line" means last line with something
- * on it... we try to skip comments.
- *
- * This latter clause conforms to the common practice of writing
- * (DEFUN FUNGUS(X Y)
- * (COND ...
- * rather than the less compact
- * (DEFUN FUNGUS(X Y)
- * (COND ...
- */
-
-
- lisp_compute_indent()
- { int begline, backone, prevcol, leftcol, col, s, i;
- int orig = point;
-
- if (lisp_indent_mode == 0) return 0;
-
- to_begin_line(); /* Go to start of this line. */
- begline = point;
-
- /* If the line starts in column 0, presume indent of 0: */
- if (character(point) > ' ') return 0;
-
- if (point == 0) { point=orig; return 0; }
-
- if (lisp_indent_mode == 1)
- { --point;
- to_indentation();
- leftcol = current_column();
- goto cur;
- }
-
- /* We're left with some variant of my heuristic. If others are
- * added, we should dispatch (eg) on lisp_indent>mode >> 5.
- */
-
- s = backward_thing(); /* Move back one S-expr */
- backone = point;
- /* If we've moved to column 0, or if we were blocked (meaning
- * beginning of buffer), then indent is 0.
- */
- leftcol = 0;
- if ((s == 0) ||
- ((s == 1) && (current_column() == 0))) goto cur;
-
- if ((prevcol=current_column()) == 0) goto cur;
-
- /* Now we're presumably on previous line (more precisely, on
- * the line at which the previous "thing" begins). See if this
- * is the line which starts a definition (ie, in column 1): */
-
- if ((lisp_indent_mode&1) == 0)
- { to_begin_line(); /* Start of previous line. */
- if (character(point) == '(')
- { point=orig;
- return lisp_tab_size;
- }
- }
-
- /* OK, use the general case: start of previous expression. */
- point = backone;
-
- leftcol = prevcol;
-
- if ((lisp_indent_mode&2) == 0)
- { /* Narrow-mode hack - try to go back a thing, finding the
- * first of several on the line:
- */
- i = -1;
- while (backward_thing() == 1)
- { if ((col=current_column()) >= leftcol) break;
- i = leftcol;
- leftcol = col;
- }
- if (((lisp_indent_mode&8) == 0) && (i > 0)) leftcol = i;
- }
-
-
- if ((lisp_indent_mode&4) == 0)
- { point = backone;
-
- if (s == 2) point = backone;
- else
- { point = orig;
- if (lisp_move_level(-1, RIGHTD, LEFTD) <= 0) goto cur;
- }
-
- col = current_column();
- if (character(point+1) == '(') leftcol = col+1;
- else leftcol = col+lisp_tab_size;
- }
-
- cur: point = orig;
- return leftcol;
- }
-
- /* Move to the beginning of current word....
- * added since I couldn't get backward_word to do what I wanted, for
- * unknown reasons.
- */
- lisp_word_beginning()
- { char ch;
- while ((point>0) && (character(point) <= ' ')) --point;
- while (point>0)
- { --point;
- ch = character(point);
- switch(ch)
- { case '(': case ')':
- case ' ': case '\t': case '\n':
- ++point;
- return 1;
- default: continue;
- }
- }
- return 0;
- }
-
- /* We should have real move-expression primitives, which know about
- * comment syntax etc. Unfortunately, we're reduced to ersatz
- * heuristics...
- * Returns 0 if blocked, 1 on success, 2 if it pops out of a (...)
- */
- backward_thing()
- { char ch;
- int posn, orig, result;
-
- again: orig = point;
- result = 0;
- for (;;)
- { if (!point) return 0;
- --point;
- switch (ch = character(point))
- { case ' ':
- case '\n':
- case '\t': continue;
- case ')': ++point;
- if (move_level(-1, ")", "(")) result=1;
- goto gotit;
- case '(': return 2;
- default: ++point;
- lisp_word_beginning();
- if (character(point) == '(') result=2;
- else result=1;
- goto gotit;
- }
- }
-
-
- gotit:
- /* Now, the problem is that we might have found a word inside
- * of a comment. Check for this, and punt if necessary
- */
-
- posn = point; /* Where we've moved to */
- to_begin_line(); /* Go to start of new line. */
- if (search(1, ";"))
- {
- if ((point < posn) && (point < orig) &&
- (character(point-1) == ';'))
- {
- --point; --point;
- goto again;
- }
- }
- point = posn;
- return result;
- }
-
-
- /* Compute column of next tab stop, assuming we're in column n:
- */
- lisp_tab_after(n)
- { return lisp_tab_size * (1+(n/lisp_tab_size));
- }
-
- /* Move in direction dir to find a parenthesis that would
- * match first at point. Return 1 on success. Otherwise go to
- * starting point, and return 0.
- */
-
- lisp_move_level(dir, first, second)
- char first, second;
- { int orig = point;
- int level = -dir; /* hack for up & down level */
- char pat[6]; /* temporary pattern */
-
- sprintf(pat, "[%c%c]", first, second);
- while (re_search(dir, pat)) /* look for either first or second */
- { if (character(point - (dir > 0)) == first) level++;
- else level--;
- if (level == 0) return 1; /* when we return to same level,done*/
- }
-
- point = orig;
- return 0;
- }
-
- /* Find start of current top-level form, using the heuristic that such
- * forms conventionally start with a left paren in column 0.
- */
- lisp_find_top_open()
- { int orig = point;
-
- while (point > 0)
- { to_begin_line();
- if (character(point) == '(') return 1;
- if (point > 0) --point;
- }
- say("No ( at left margin!");
- point = orig;
- return 0;
- }
-
-
- /************************************************************************
- * *
- * Lisp mode commands: *
- * *
- ************************************************************************/
-
- /* Set point to the start of the top-level form, and mark to its end.
- * Returns 1 iff successful, else 0.
- */
-
- command lisp_select_top_form() on lisp_tab[ALT('g')]
- { int start, end;
- if (!lisp_find_top_open()) return 0;
- start = point;
- if (!move_level(1, "(", ")")) return 0;
- end = point;
- mark = end;
- point = start;
- return 1;
- }
-
- command lisp_top_beginning() on lisp_tab[ALT('a')]
- { if (!lisp_find_top_open()) return 0;
- return 1;
- }
-
- command lisp_top_end() on lisp_tab[ALT('e')]
- { if (lisp_select_top_form()) point = mark;
- }
-
-
-
- /* re-format current definition:
- * with arg, reformats current expression.
- * Repeated uses change lisp_indent_mode before reformatting.
- */
-
- command lisp_reformat() on lisp_tab[ALT('\t')]
- { int *start = alloc_spot(), *end = alloc_spot(),
- *orig=alloc_spot(), rtn=0;
- char buf[30];
- int i, j;
-
- *orig = point;
-
- /* On repeated uses, select new indentation mode: */
- if ((prev_cmd == LISP_REFORMAT_TOP) ||
- (prev_cmd == LISP_REFORMAT_SUB)) lisp_choose_new_mode();
-
- /* Should we reformat current sub-expression? */
- if ((iter != 1) || (prev_cmd == LISP_REFORMAT_SUB))
- { if (character(point) != '(')
- { while (backward_thing() == 1);
- }
- *start = point;
- if (!move_level(1, "(", ")")) goto err;
- *end = point;
- iter = 1;
- this_cmd = LISP_REFORMAT_SUB;
- }
-
- else /* Nope, reformat entire top-level sexp */
- { if (!lisp_select_top_form()) goto err;
- *start = point;
- *end = mark;
- this_cmd = LISP_REFORMAT_TOP;
- }
-
-
- /* Let user know we're working on it... */
- point = *start;
- for (i=0, j=0; j<29; i++)
- { buf[j] = character(point+i);
- switch (buf[j++])
- { case '\n': --j; goto gotit;
- case '\t': buf[j-1] = ' '; continue;
- }
- }
- gotit: buf[j] = 0;
- say("Re-formatting %s... using lisp_indent_mode = %d",
- buf, lisp_indent_mode);
-
- /* Loop through each line, reformatting it: */
- while (point < *end)
- { lisp_indenter();
- nl_forward();
- }
-
- err: point = *start;
-
- /* Correct apparent bug: spot seems to drift into white space
- * to left, during reformatting:
- */
- for (;;)
- { i = character(point);
- if ((i != ' ') && (i != '\t')) break;
- ++point;
- }
-
- mark = *end;
-
- say("%s... reformatted in lisp_indent_mode %d",
- buf, lisp_indent_mode);
- free_spot(orig);
- free_spot(start);
- free_spot(end);
- return rtn;
- }
-
- /* Cycle thru interesting indentation modes, re-formatting the current
- * definition each time.
- */
-
- int lisp_indent_modes[] =
- { 28, 19, 20, 22, 23, 0
- };
-
- lisp_choose_new_mode()
- { int i;
- for (i=0; lisp_indent_modes[i]; i++)
- { if (lisp_indent_mode == lisp_indent_modes[i])
- { lisp_indent_mode = lisp_indent_modes[i+1];
- goto gotmode;
- }
- }
- lisp_indent_mode = 0;
- gotmode:
- if (lisp_indent_mode == 0) lisp_indent_mode = lisp_indent_modes[0];
- }
-
- command lisp_indenter()
- {
- to_indentation(); /* this seems to be needed, why? */
- to_column(lisp_compute_indent());
- }
-
- /* Indent an existing line of lisp code.
- * If our new indentation matches the old, just insert a tab.
- * if we're not in this line's indentation indent the line,
- * but keep point where it was -- bkph.
- */
-
- command lisp_indent() on lisp_tab['\t']
- { int orig = point;
- int orig_column = current_column(), cur_column, new_indent;
- int restore_point = 0;
- int cmd, n;
-
- /* Keep track of how many consecutive tabs have been typed: */
- if ((prev_cmd==LISP_TAB_1) || (prev_cmd==LISP_TAB_2))
- this_cmd = LISP_TAB_2;
- else this_cmd = LISP_TAB_1;
-
-
- if (lisp_tab_mode == 0) /* Special case... */
- {
- stupid_tab: /* Just treat as conventional tab... */
- point = orig;
- insert('\t');
- return;
- }
-
- to_indentation();
- cur_column = current_column();
- if (orig_column>cur_column)
- { /* Here if not in indentation... */
-
- restore_point = 1;
- switch(lisp_tab_mode & 3)
- { case 0:
- goto stupid_tab;
- case 1:
- goto simu_tab;
- default: break;
- }
- }
-
-
- /* Extract appropriate 3-bit field from lisp_tab_mode to
- * advise us in current situation: */
-
- cmd = lisp_tab_mode >> 3;
- switch (prev_cmd)
- { case LISP_TAB_2: cmd = cmd >> 3;
- case LISP_TAB_1: cmd = cmd >> 3;
- default: break;
-
- }
-
- /* Dispatch on command bits: */
-
- cmd = cmd & 7;
- switch (cmd)
- { case 0: goto stupid_tab;
- case 1: new_indent = lisp_compute_indent();
- goto set_indent;
- case 2: new_indent = lisp_tab_after(cur_column);
- goto set_indent;
-
- case 3: new_indent = lisp_tab_size;
- goto set_indent;
- case 4: new_indent = 0;
- goto set_indent;
- default:
- simu_tab:
- case 5: point = orig;
- cur_column = current_column();
- n = lisp_tab_after(cur_column);
- do (insert(' '));
- while (current_column() < n);
- return;
- }
-
- set_indent:
- /* Set mark to point, allowing original position to be restored
- * even if theres insertion/deletion. */
- mark = orig;
-
- to_column(new_indent);
- oldpos: /* Restore previous position (modulo insertions/deletions), and
- * return: */
- if (restore_point) point=mark;
-
- newpos: return;
-
- }
-
- command move_backward_thing() on lisp_tab[ALT('b')]
- { int it = backward_thing();
- /* debugging printout, no longer needed:
- switch(it)
- { case 2: say("Moved up a level"); break;
- case 1: say("OK."); break;
- case 0: say("Blocked"); break;
- default: say("It = %d", it); break;
- }
- */
- }
-
- command lisp_comment() on lisp_tab[ALT(';')]
- { int *svmark = alloc_spot();
- *svmark = mark;
- end_of_line();
- if (lisp_comment_col > (current_column() + 1))
- to_column(lisp_comment_col);
- else insert(' ');
- insert(';');
- insert(' ');
- mark = *svmark;
- free_spot(svmark);
- }
-
- /* Some old stuff:
- */
-
- command lisp_up_level() on lisp_tab[ALT(CTRL('u'))]
- { lisp_move_level(-1,RIGHTD,LEFTD);
- }
-
- command lisp_down_level() on lisp_tab[ALT(CTRL('d'))]
- { lisp_move_level(1,LEFTD,RIGHTD); /* not quite right */
- }
-
- /* Enter LISP mode:
- */
- command lisp_mode()
- {
- mode_keys = lisp_tab; /* use these keys */
- lisp_tab[')'] = (short) show_matching_delimiter;
- lisp_tab[']'] = (short) show_matching_delimiter;
- lisp_tab['}'] = (short) show_matching_delimiter;
- lisp_tab[CTRL('H')] = (short) backward_delete_character;
- major_mode = strsave("Lisp");
- make_mode();
- indenter = lisp_indenter;
- auto_indent = 1;
- margin_right = 80;
- delete_hacking_tabs = 1;
- }
-
- /* make this the default mode for .LSP (Lisp) and .S (Scheme) files */
-
- suffix_lsp() { lisp_mode(); }
- suffix_s() { lisp_mode(); }
- suffix_scm() { lisp_mode(); }
-
-
- /*** End ***/
-